package com.atguigu.gmall.list.service.impl;

import com.alibaba.fastjson.JSON;
import com.atguigu.gmall.list.dao.GoodsRepositoryDao;
import com.atguigu.gmall.list.service.SearchService;
import com.atguigu.gmall.model.list.*;
import com.atguigu.gmall.model.product.BaseCategoryView;
import com.atguigu.gmall.model.product.BaseTrademark;
import com.atguigu.gmall.model.product.SkuAttrValue;
import com.atguigu.gmall.model.product.SkuInfo;
import com.atguigu.gmall.product.client.ProductFeignClient;
import com.sun.org.apache.xpath.internal.operations.Bool;
import org.apache.lucene.search.join.ScoreMode;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.Operator;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.aggregations.Aggregation;
import org.elasticsearch.search.aggregations.AggregationBuilder;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.bucket.nested.ParsedNested;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedLongTerms;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedStringTerms;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import org.elasticsearch.search.aggregations.bucket.terms.Terms.Bucket;

import java.util.*;
import java.util.stream.Collectors;

/**
 * 搜索管理
 */
@Service
public class SearchServiceImpl implements SearchService {

    @Autowired
    private ProductFeignClient productFeignClient;
    @Autowired
    private GoodsRepositoryDao goodsRepositoryDao;
    @Autowired
    private RedisTemplate redisTemplate;
    //    @Autowired  //SpringDataES
//    private ElasticsearchRestTemplate elasticsearchRestTemplate;//Spring公司封装的
    @Autowired
    private RestHighLevelClient restHighLevelClient;//原生API

    //上架商品到索引库中
    @Override
    public void upperGoods(Long skuId) {
        //1:Sku信息
        SkuInfo skuInfo = productFeignClient.getSkuInfo(skuId);
        //2:sku对应的一二三分类的对象
        BaseCategoryView categoryView = productFeignClient.getCategoryView(skuInfo.getCategory3Id());
        //3:Sku相关的平台属性及属性值 集合
        List<SkuAttrValue> attrList = productFeignClient.getAttrList(skuId);
        //4:Sku相关的品牌信息
        BaseTrademark trademark = productFeignClient.getTrademark(skuInfo.getTmId());
        //Goods  对象 与ES索引库 mapping映射关系
        Goods goods = new Goods();

        //设置SkuInfo相关数据
        goods.setId(skuInfo.getId());
        goods.setDefaultImg(skuInfo.getSkuDefaultImg());
        goods.setTitle(skuInfo.getSkuName());
        goods.setPrice(skuInfo.getPrice().doubleValue());
        //设置当前保存索引时候的时间
        goods.setCreateTime(new Date());
        //设置品牌相关信息
        goods.setTmId(trademark.getId());
        goods.setTmName(trademark.getTmName());
        //设置一二三级分类的ID 名称
        goods.setCategory1Id(categoryView.getCategory1Id());
        goods.setCategory1Name(categoryView.getCategory1Name());
        goods.setCategory2Id(categoryView.getCategory2Id());
        goods.setCategory2Name(categoryView.getCategory2Name());
        goods.setCategory3Id(categoryView.getCategory3Id());
        goods.setCategory3Name(categoryView.getCategory3Name());
        //设置平台属性 及属性值
        //List<SearchAttr> attrs;//平台属性 及属性值  这是我们需要的
        //List<SkuAttrValue> attrList   这是我们现在手里有的数据
        //学习了 Lomda表达式
        List<SearchAttr> searchAttrList = attrList.stream().map(sav -> {
            SearchAttr searchAttr = new SearchAttr();
            //平台属性ID
            searchAttr.setAttrId(sav.getAttrId());
            //平台属性名称
            searchAttr.setAttrName(sav.getBaseAttrInfo().getAttrName());
            //平台属性值名称
            searchAttr.setAttrValue(sav.getBaseAttrValue().getValueName());
            return searchAttr;
        }).collect(Collectors.toList());
        goods.setAttrs(searchAttrList);
        //保存ES
        goodsRepositoryDao.save(goods);

    }

    //下架商品 删除索引库
    @Override
    public void lowerGoods(Long skuId) {
        goodsRepositoryDao.deleteById(skuId);
    }

    //增加商品的热度
    @Override
    public void hosScore(Long skuId) {
        //1~9  11~19 21~29 不更新索引库   更新缓存  10 20 30 40 更新索引库
        String name = "hosScore";
        // zset 有序集合  Set hosScore = new ...
        //参数1：集合名称  参数2：集合的值的名称  参数3：分数    小明   语文 100  数学 98 英文 90
        Double totalScore = redisTemplate.opsForZSet().incrementScore(name, skuId, 1);
        //判断是否更新索引库
        if (totalScore % 10 == 0) {
            //更新索引   没有更新概念 更新：先删除再添加
            Optional<Goods> optional = goodsRepositoryDao.findById(skuId);
            Goods goods = optional.get();
            //设置热度分
            goods.setHotScore(Math.round(totalScore));//4.5分  5分 4分     4.3分 5 4
            //保存  1）先判断保存的数据中的主键是否存在 如果存在了 更新 如果不存在 就是保存
            goodsRepositoryDao.save(goods);
        }

    }

    //根据入参对象搜索索引库并返回结果
    @Override
    public SearchResponseVo search(SearchParam searchParam) throws Exception {
        //1：设置搜索条件
        SearchRequest searchRequest = buildDslQuery(searchParam);
        //2;执行搜索
        SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
        //3:处理搜索结果    1）商品集合 2）分页 总条数  3）品牌集合 4）平台属性及属性值集合
        SearchResponseVo vo = parseResponse(searchResponse);
        //当前页
        Integer pageNo = searchParam.getPageNo();
        vo.setPageNo(pageNo);
        //每页显示的条数
        Integer pageSize = searchParam.getPageSize();
        vo.setPageSize(pageSize);
        //总条数
        Long total = vo.getTotal();
        //总页数
        Long totalPages = (total + pageSize -1)/pageSize;
        vo.setTotalPages(totalPages);
        return vo;
    }

    //处理搜索结果  解析搜索结果
    private SearchResponseVo parseResponse(SearchResponse searchResponse) {
        //结果对象  1）商品集合 2）分页
        SearchResponseVo vo = new SearchResponseVo();
        //商品集合
        SearchHits hits = searchResponse.getHits();
        //总条数
        long totalHits = hits.totalHits;
        vo.setTotal(totalHits);
        SearchHit[] hits1 = hits.getHits();
        if(null != hits1 && hits1.length > 0){
            List<Goods> goodsList = Arrays.stream(hits1).map(sh -> {
                Goods goods = JSON.parseObject(sh.getSourceAsString(), Goods.class);
                //获取高亮  判断  有高亮使用高亮  没有高亮使用普通名称
                Map<String, HighlightField> highlightFields = sh.getHighlightFields();
                if (null != highlightFields && highlightFields.size() > 0) {
                    HighlightField title = highlightFields.get("title");
                    if (null != title) {
                        String h = title.fragments()[0].toString();
                        goods.setTitle(h);
                    }
                }
                return goods;
            }).collect(Collectors.toList());
            vo.setGoodsList(goodsList);
        }
        //从搜索响应结果对象获取分组后的  品牌集合  及  平台属性集合  长度是2
        Map<String, Aggregation> aggregationMap = searchResponse.getAggregations().asMap();
        //3）品牌集合
        ParsedLongTerms tmIdAgg = (ParsedLongTerms) aggregationMap.get("tmIdAgg");
        List<SearchResponseTmVo> trademarkList = tmIdAgg.getBuckets().stream().map(bucket -> {
            SearchResponseTmVo tmVo = new SearchResponseTmVo();
            //品牌ID
            String tmId = bucket.getKeyAsString();
            tmVo.setTmId(Long.parseLong(tmId));
            //品牌名称
            ParsedStringTerms tmNameAgg = bucket.getAggregations().get("tmNameAgg");
            String tmName = tmNameAgg.getBuckets().get(0).getKeyAsString();
            tmVo.setTmName(tmName);
            return tmVo;
        }).collect(Collectors.toList());
        vo.setTrademarkList(trademarkList);
        //4）平台属性及属性值集合
        ParsedNested attrsAgg = (ParsedNested) aggregationMap.get("attrsAgg");
        ParsedLongTerms attrIdAgg = attrsAgg.getAggregations().get("attrIdAgg");
        //平台属性ID
        List<SearchResponseAttrVo> attrVoList = attrIdAgg.getBuckets().stream().map(bucket -> {
            SearchResponseAttrVo attrVo = new SearchResponseAttrVo();
            //1:平台属性ID
            String attrId = bucket.getKeyAsString();
            attrVo.setAttrId(Long.parseLong(attrId));
            //2:平台属性名称
            ParsedStringTerms attrNameAgg = bucket.getAggregations().get("attrNameAgg");
            String attrName = attrNameAgg.getBuckets().get(0).getKeyAsString();
            attrVo.setAttrName(attrName);
            //3：平台属性值集合
            ParsedStringTerms attrValueAgg = bucket.getAggregations().get("attrValueAgg");
            List<String> attrValueList = attrValueAgg.getBuckets().
                    stream().map(Bucket::getKeyAsString).collect(Collectors.toList());
            attrVo.setAttrValueList(attrValueList);
            return attrVo;
        }).collect(Collectors.toList());
        vo.setAttrsList(attrVoList);
        return vo;
    }

    //专门条件构建的方法
    private SearchRequest buildDslQuery(SearchParam searchParam) {

        //搜索资源构建对象 (将条件全部收集上来）
        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();

        //条件的组合对象  来组合大量条件
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();

        //1) 设置关键词  1）我  and    2）要  or    3）一部       4）手机
        //   1）三星  AND  2）手机
        String keyword = searchParam.getKeyword();
        if (!StringUtils.isEmpty(keyword)) {
            //设置关键词到组合对象中   分词后模糊查询再交集 and
            boolQueryBuilder.must(QueryBuilders.matchQuery("title", keyword)
                    .operator(Operator.AND));
        }
        //2）设置品牌条件  trademark = 2:华为
        String trademark = searchParam.getTrademark();
        if (!StringUtils.isEmpty(trademark)) {
            String[] t = StringUtils.split(trademark, ":");
            if (t != null && t.length == 2) {
                boolQueryBuilder.filter(QueryBuilders.termQuery("tmId", t[0]));
            }
        }
        //3)分类 一二三 ID
        Long category1Id = searchParam.getCategory1Id();
        Long category2Id = searchParam.getCategory2Id();
        Long category3Id = searchParam.getCategory3Id();
        if (null != category1Id) {
            boolQueryBuilder.filter(QueryBuilders.termQuery("category1Id", category1Id));
        }
        if (null != category2Id) {
            boolQueryBuilder.filter(QueryBuilders.termQuery("category2Id", category2Id));
        }
        if (null != category3Id) {
            boolQueryBuilder.filter(QueryBuilders.termQuery("category3Id", category3Id));
        }
        //4:平台属性及属性值  平台属性ID:平台属性值:平台属性名称   2:6.75-6.84英寸:屏幕尺寸
        String[] props = searchParam.getProps();
        if (null != props && props.length > 0) {
            for (String prop : props) {
                BoolQueryBuilder subBoolQueryBuilder = QueryBuilders.boolQuery();
                //平台属性来循环
                String[] p = prop.split(":");
                if (null != p && p.length == 3) {
                    //组合对象
                    subBoolQueryBuilder.must(QueryBuilders.termQuery("attrs.attrId", p[0]));
                    subBoolQueryBuilder.must(QueryBuilders.termQuery("attrs.attrValue", p[1]));
                }
                boolQueryBuilder.filter(QueryBuilders.nestedQuery("attrs", subBoolQueryBuilder, ScoreMode.None));
            }
        }
        //将组合对象设置到资源构建对象中
        sourceBuilder.query(boolQueryBuilder);
        //以上内容为条件组合完成  （不包括排序 不包括分页、不包括高亮、不包括分组）




        //排序 order=1:asc  或是 1:desc 或是 2:asc 或是 2:desc
        String order = searchParam.getOrder();
        if (!StringUtils.isEmpty(order)) {
            String[] o = StringUtils.split(order, ":");
            if (null != o && o.length == 2) {
                String sort = null;
                switch (o[0]) {
                    case "1":
                        sort = "hotScore";
                        break;
                    case "2":
                        sort = "price";
                        break;
                }
                sourceBuilder.sort(sort,
                        o[1].equalsIgnoreCase("asc") ? SortOrder.ASC : SortOrder.DESC);
            }
        } else {
            //为NULL或是空串  默认排序  热度或热点排序   热点高的排在最前面
            sourceBuilder.sort("hotScore", SortOrder.DESC);

        }
        //分页
        Integer pageNo = searchParam.getPageNo();
        Integer pageSize = searchParam.getPageSize();
        //设置开始行   select *  from 表 limit 开始行,每页数
        sourceBuilder.from((pageNo-1)*pageSize);
        //设置每页数
        sourceBuilder.size(pageSize);
        //高亮
        HighlightBuilder highlightBuilder = new HighlightBuilder();
        //设置高亮的字段
        highlightBuilder.field("title").preTags("<font color='red'>").postTags("</font>");
        sourceBuilder.highlighter(highlightBuilder);

        //分组查询品牌
        sourceBuilder.aggregation(AggregationBuilders.
                terms("tmIdAgg").field("tmId").
                subAggregation(AggregationBuilders.terms("tmNameAgg").field("tmName")));
        //分组查询 平台属性及属性值
        sourceBuilder.aggregation(
                AggregationBuilders.nested("attrsAgg", "attrs")
                        .subAggregation(AggregationBuilders.terms("attrIdAgg").field("attrs.attrId")
                                .subAggregation(AggregationBuilders.terms("attrNameAgg").field("attrs.attrName"))
                                .subAggregation(AggregationBuilders.terms("attrValueAgg").field("attrs.attrValue"))));


        //搜索请求对象  设置 索引库的名称
        SearchRequest searchRequest = new SearchRequest("goods");//指定的要索引的索引库名称
        //设置索引库中type的名称
        searchRequest.types("info");
        //设置搜索资源构建对象到搜索请求对象中
        searchRequest.source(sourceBuilder);

        return searchRequest;
    }
}
